:- import "%%/asm/dumper.sl" #
:- import "%%/asm/platform.sl" #
:- import "%%/asm/tables.sl" #


-- Flow Constructs --

Generate .tree :-
	Register.Reset,
	Generate.Declarations $ .tree #

Generate.Declarations . $ :- ! #
Generate.Declarations .scope (.decl, .remains) :-
	Generate.Declaration .scope .decl,
	Generate.Declarations .scope .remains #

Generate.TempTypeName .typeName :-
	temporary .typeNum, concat "T" .typeNum .typeName #

Generate.Declaration .scope .decl :-
	select (.decl = TYPEDEF .type .name =>
		.typeName = .name
	) (.decl = VARDEF .type .name =>
		Generate.TempTypeName .typeName
	) (.decl = VARDEFVALUE .type .name . =>
		Generate.TempTypeName .typeName
		-- TODO detects in check condition: no VARDEFVALUE in root
	),
	Generate.Type (SCOPE .typeName .scope) .type #

Generate.Type .scope (CLASS .members) :- !
	Generate.Declarations .scope .members #
Generate.Type .scope .function :-
	nth .function 0 FUNC !
	Generate.ScopeName .scope .name,
	Emit (LABEL .name),
	Generate.Function .function #
Generate.Type . . #

Generate.ScopeName (SCOPE .cur $) .cur :- ! #
Generate.ScopeName (SCOPE .cur .remain) .name :-
	Generate.ScopeName .remain .pre,
	concat .pre "$" .cur .name ! #

	-- Takes care also prolog/epilog code
Generate.Function (FUNC . . .block) :-
	Config.PointerSize .ps,
	Emit ("PUSH" (GR .ps 5)),
	Emit ("MOV" (GR .ps 5) (GR .ps 4)),
	Generate.Statements .block (0 => .),
	Emit ("MOV" (GR .ps 4) (GR .ps 5)),
	Emit ("POP" (GR .ps 5)),
	Emit "RET" #

	-- .fs is current frame size
Generate.Statements $ (.fs => .fs) :- ! #
Generate.Statements (.stmt, .remains) (.fs0 => .fs1) :- !
	Generate.Statement .stmt (.fs0 => .fs),
	Generate.Statements .remains (.fs => .fs1) #

Generate.Statement (BLOCK .stmts) (.fs => .fs1) :- !
	Generate.Statements .stmts (.fs => .fs1) #
Generate.Statement (IF .cond .then) (.fs => .fs) :- !
	Generate.Expression .cond .op .fs,
	Label.New .endLabel,
	Generate.JumpIfZero .op .endLabel,
	Op.Free .op,
	Generate.Statement .then (.fs => .),
	Emit .endLabel ! #
Generate.Statement (IFELSE .cond .then .else) (.fs => .fs) :- !
	Generate.Expression .cond .op .fs,
	Label.New .elseLabel, Label.New .endLabel,
	Generate.JumpIfZero .op .elseLabel,
	Op.Free .op,
	Generate.Statement .then (.fs => .),
	Emit ("JMP" .endLabel),
	Emit .elseLabel,
	Generate.Statement .else (.fs => .),
	Emit .endLabel ! #
Generate.Statement (WHILE .cond .do) (.fs => .fs) :- !
	Label.New .beginLabel, Label.New .exitLabel,
	Emit .beginLabel,
	Generate.Expression .cond .op .fs,
	Generate.JumpIfZero .op .exitLabel,
	Op.Free .op,
	Generate.Statement .do (.fs => .),
	Emit ("JMP" .beginLabel),
	Emit .exitLabel ! #
Generate.Statement (VARDEF .type .) (.fs0 => .fs1) :- !
    Type.Size .type .size,
    Config.PointerSize .ps,
    Emit ("SUB" (GR .ps 4) (IMM .size)),
    let .fs1 (.fs0 - .size) #
Generate.Statement (VARDEFVALUE .type .var .value) (.fs0 => .fs1) :- !
	Generate.Statement (VARDEF .type .var) (.fs0 => .fs),
	Generate.Expression (ASGop (VAR .var) .value) (.fs => .fs1) #
Generate.Statement (EXPR .expression) (.fs => .fs) :-
	Generate.Expression .expression .op .fs,
	Op.Free .op #

Generate.Expression (CALL .func .params) (.ret) .fs :- !
	Generate.PushParameters .params .fs,
	Config.WordSize .size, .EAX = GR .size 0,

	-- Ugly. We shan't use the low level register allocators.
	if (not search (REG.AVAIL .EAX)) then ( -- Saves EAX if required
		Emit ("PUSH" .EAX)
	),
	Emit ("CALL" .func),
	if (not search (REG.AVAIL .EAX)) then (
		Op.MakeCopy .EAX .ret,
		Emit ("POP" .EAX)
	) else (
		Register.GetTransient .EAX, -- Marks used
		.ret = .EAX
	) #

Generate.PushParameters $ . :- ! #
Generate.PushParameters (.var, .remains) .fs :-
	Generate.Expression .var .op .fs,
	Emit ("PUSH" .op),
	Op.Free .op,
	Generate.PushParameters .remains .fs #


-- Arithmetics --

	-- We use the plain old allocate-on-demand and return operand method for
	--  generating code.  Not exactly optimal but quite usable.

	-- One drawback is we cannot use registers to put locals/parameters.
	-- It could get deallocated and reused in Op.Free.  Until SSA is ready...

Generate.Expression (.op .left .right) (.ret) .fs :- !
	Generate.Expression .left .op1 .fs,
	Generate.Expression .right .op2 .fs,
	select
		(.op = ASGop => Generate.Assign		.op1 .op2 .ret)
		(.op = ADDop => Generate.Add		.op1 .op2 .ret)
		(.op = SUBop => Generate.Subtract	.op1 .op2 .ret) #

Generate.Assign .op1 .op2 $ :-
	not Op.Mutable .op1,
	Op.Transfer .op1 .op2,
	Op.Free .op1, Op.Free .op2 ! #

Generate.Add (IMM .i1) (IMM .i2) (IMM .n) :-
	let .n (.i1 + .i2) ! #
Generate.Add .op1 .op2 (.op1) :-
	Op.Mutable .op1, Emit ("ADD" .op1 .op2), Op.Free .op2 ! #
Generate.Add .op1 .op2 (.op2) :-
	Op.Mutable .op2, Emit ("ADD" .op2 .op1), Op.Free .op1 ! #
Generate.Add .op1 .op2 (.trans) :-
	Op.MakeCopy .op1 .trans, Emit ("ADD" .trans .op2) #

Generate.Subtract (IMM .i1) (IMM .i2) (IMM .n) :-
	let .n (.i1 - .i2) ! #
Generate.Subtract .op1 .op2 (.op1) :-
	Op.Mutable .op1, Emit ("SUB" .op1 .op2), Op.Free .op2 ! #
Generate.Subtract .op1 .op2 (.op2) :-
	Op.Mutable .op2, Emit ("NEG" .op2), Emit ("ADD" .op2 .op1), Op.Free .op1 ! #
Generate.Subtract .op1 .op2 (.trans) :-
	Op.MakeCopy .op1 .trans, Emit ("SUB" .trans .op2) #


-- Pointers --

Generate.Expression (PTR .expr) (MEM .reg $ () 0) .fs :- !
	Generate.Expression .expr .op0 .fs,
	if (Op.Register .op0) then (
		.reg = .op0
	) else (
		Op.Transient .reg,
		Op.Transfer .reg .op0
	) #


-- Variables & Constants --

Generate.Expression (FRAMED .offset) .op . :- ! Op.Framed .offset .op #
Generate.Expression (NUM .n) (IMM .n) . #

Generate.Address (FRAMED .offset) (.ret) . :- !
	Register.GetTransient .ret,
	Op.Framed .offset .op,
	Emit ("LEA" .ret .op) ! #
Generate.Address (VAR .var) (.ret) .fs :-
	Register.GetTransient .ret,
	Generate.Global .var .op .fs,
	Emit ("LEA" .ret .op) ! #
Generate.Address . (.) . :-
	Error "Expression is not a Lvalue" ! #

	-- Variable generation returns a Lvalue operand; i.e. memory location
Generate.Global .var (MEM $ $ 1 .address) . :- -- Otherwise should be global
	search (Global .var .address);
	(get DATASIZE .address; .address = 0), -- Allocates space for global
	let .a1 (.address + 4), set DATASIZE .a1,
	insertz (Global .var .address) ! #


-- Miscellaneous --

Generate.JumpIfZero .op .label :-
	let .i .op,
	if (.i = 0) then (
		Emit ("JMP" .label)
	) #
Generate.JumpIfZero .op .label :-
	Register .op,
	Emit ("AND" .op .op),
	Emit ("JZ" .label) ! #
Generate.JumpIfZero .op .label :- (
		Op.Register .op, Emit ("OR" .op .op);
		Emit ("CMP" .op (IMM 0))
	),
	Emit ("JE" .label) #


-- Label Processing --

Label.New (LABEL .label) :-
	temporary .labelNo, concat "L" .labelNo .label ! #
